package com.googlecode.gwtbb.server.intermediate.search;

import com.googlecode.gwtbb.server.dao.EntityHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;

import java.io.IOException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author Abraham Grief
 * @version $Id$
 * @since Jul 29, 2008
 */
public abstract class DirectoryIndexer<E> {
	private static Log log = LogFactory.getLog(DirectoryIndexer.class);
	protected IndexWriter indexWriter;
	private Directory directory;
	private Analyzer analyzer;
	private int flushInitialDelayMillis = 60000;
	private int flushDelayMillis = 60000;
	private ScheduledExecutorService flushExecutor;
	private ScheduledFuture flushFuture;
	private AddIndexHandler addIndexHandler = new AddIndexHandler();
	private DeleteIndexHandler deleteIndexHandler = new DeleteIndexHandler();
	private UpdateIndexHandler updateIndexHandler = new UpdateIndexHandler();

	public AddIndexHandler getAddIndexHandler() {
		return addIndexHandler;
	}

	public Analyzer getAnalyzer() {
		return analyzer;
	}

	public void setAnalyzer(Analyzer analyzer) {
		this.analyzer = analyzer;
	}

	public DeleteIndexHandler getDeleteIndexHandler() {
		return deleteIndexHandler;
	}

	public Directory getDirectory() {
		return directory;
	}

	public void setDirectory(Directory directory) {
		this.directory = directory;
	}

	public int getFlushDelayMillis() {
		return flushDelayMillis;
	}

	public void setFlushDelayMillis(int flushDelayMillis) {
		this.flushDelayMillis = flushDelayMillis;
	}

	public ScheduledExecutorService getFlushExecutor() {
		return flushExecutor;
	}

	public void setFlushExecutor(ScheduledExecutorService flushExecutor) {
		this.flushExecutor = flushExecutor;
	}

	public int getFlushInitialDelayMillis() {
		return flushInitialDelayMillis;
	}

	public void setFlushInitialDelayMillis(int flushInitialDelayMillis) {
		this.flushInitialDelayMillis = flushInitialDelayMillis;
	}

	public UpdateIndexHandler getUpdateIndexHandler() {
		return updateIndexHandler;
	}

	public void add(E element) throws IOException {
		indexWriter.addDocument(createDocument(element));
	}

	public synchronized void close() throws IOException {
		indexWriter.close();
		indexWriter = null;
		flushFuture.cancel(false);
		flushFuture = null;
	}

	public void createAndOpenIndex() throws IOException {
		log.info("creating index...");
		indexWriter = new IndexWriter(directory, true, analyzer, true);
		log.info("index created.  adding documents...");
		addAllDocuments();
		log.info("documents added to index. optimizing...");
		optimizeIndex();
		log.info("index optimized.  index creation complete.");
		flushFuture = flushExecutor.scheduleWithFixedDelay(new Runnable() {
			public void run() {
				try {
					indexWriter.flush();
				} catch (IOException e) {
					log.error("could not flush indexWriter", e);
				}
			}
		}, flushInitialDelayMillis, flushDelayMillis, TimeUnit.MILLISECONDS);
	}

	public void delete(E element) throws IOException {
		indexWriter.deleteDocuments(createIdTerm(element));
	}

	public void openIndex() throws IOException {
		indexWriter = new IndexWriter(directory, true, analyzer, false);
	}

	public void optimizeIndex() throws IOException {
		indexWriter.optimize();
	}

	public void update(E element) throws IOException {
		indexWriter.updateDocument(createIdTerm(element), createDocument(element));
	}

	protected abstract void addAllDocuments();

	protected abstract Document createDocument(E element);

	protected abstract Term createIdTerm(E element);

	public class AddIndexHandler implements EntityHandler<E> {
		public void handle(E element) throws Exception {
			add(element);
		}
	}

	public class DeleteIndexHandler implements EntityHandler<E> {
		public void handle(E element) throws Exception {
			delete(element);
		}
	}

	public class UpdateIndexHandler implements EntityHandler<E> {
		public void handle(E element) throws Exception {
			update(element);
		}
	}
}
